package cn.tworice.netty.session;

import cn.tworice.netty.constand.MessageConst;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import io.netty.channel.Channel;
import lombok.Getter;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 管理Session客户端
 * 添加
 *      当Session携带gid时，不保存uid组，只保存gid组
 *      当Session不携带gid时，保存uid组，不保存gid组；并且如果重复连接，将原uid客户端断开连接。
 *
 **/
@Component
public class SessionManager {

    /**
     * <唯一标识，Session>
     **/
    private final Map<String, Session> sessionMap = new ConcurrentHashMap<>();

    /**
     * <组标识，ChannelGroup>
     **/
    private final Map<String, DefaultChannelGroup> gidChannelGroup = new ConcurrentHashMap<>();

    /**
     * <用户标识,Channel>
     **/
    private final Map<String, DefaultChannelGroup> uidChannelGroup = new ConcurrentHashMap<>();

    /**
     * 所有在线的Channel
     **/
    @Getter
    private final DefaultChannelGroup allChannelGroup = this.getDefaultChannelGroup();

    public void addChannel(String uid, String gid ,Channel channel) {
        this.addSession(Session.buildSession(uid, gid, channel));
    }

    public Session addChannel(Channel channel) {
        return this.addSession(Session.buildSession(channel));
    }

    public void addChannel(String uid, Channel channel) {
        this.addSession(Session.buildSession(uid, null, channel));
    }

    public Session addSession(Session session) {
        // 加入组Channel
        this.addSession2Group(session);
        // 存储用户Channel
        if (session.getGid() == null) {
            this.addSession2Uid(session);
        }
        // 存储唯一标识Session
        this.sessionMap.put(session.getId(), session);
        // 存储channel
        this.allChannelGroup.add(session.getChannel());
        return session;
    }

    /**
     * 删除客户端
     * @param channel 客户端
     **/
    public Session removeChannel(Channel channel) {
        Session session = this.sessionMap.remove(channel.id().asLongText());
        channel.close();
        // 一个组中所有的客户端都离线了，这个组应该被销毁
        if (session.getGid() != null && this.gidChannelGroup.get(session.getGid()).isEmpty()) {
            this.gidChannelGroup.remove(session.getGid());
        }
        if (session.getUid() != null && this.uidChannelGroup.get(session.getUid()).isEmpty()) {
            this.uidChannelGroup.remove(session.getUid());
        }
        return session;
    }

    /**
     * 删除组
     * @param gid 组标识
     **/
    public void removeGidGroup(String gid){
        for (Channel channel : this.gidChannelGroup.get(gid)) {
            channel.close();
            this.removeChannel(channel);
        }
    }

    /**
     * 删除用户
     * @param uid 用户标识
     **/
    public void removeUidGroup(String uid){
        for (Channel channel : this.uidChannelGroup.get(uid)) {
            channel.close();
            this.removeChannel(channel);
        }
    }

    /**
     * 通过组标识获取组下的所有客户端
     * @param gid 组标识
     **/
    public DefaultChannelGroup getGidGroup(String gid) {
        return this.gidChannelGroup.get(gid);
    }

    /**
     * 通过用户标识获取组下的所有客户端
     * @param uid 用户标识
     **/
    public DefaultChannelGroup getUidGroup(String uid) {
        return this.uidChannelGroup.get(uid);
    }

    private DefaultChannelGroup getDefaultChannelGroup(){
        return new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    }

    /**
     * 客户端加入组
     * @param session 客户端
     **/
    private void addSession2Group(Session session){
        if (session.getGid() != null) {
            if (!this.gidChannelGroup.containsKey(session.getGid())) {
                this.gidChannelGroup.put(session.getGid(), this.getDefaultChannelGroup());
            }
            this.gidChannelGroup.get(session.getGid()).add(session.getChannel());
        }
    }

    /**
     * 客户端加入用户组
     * @param session 客户端
     **/
    private void addSession2Uid(Session session){
        Optional.ofNullable(session.getUid()).ifPresent(uid->{
            if (this.uidChannelGroup.containsKey(uid)) {
                if(!this.uidChannelGroup.get(uid).isEmpty()){
                    this.uidChannelGroup.get(uid).close();
                }
            }else{
                this.uidChannelGroup.put(uid, getDefaultChannelGroup());
            }
            this.uidChannelGroup.get(uid).add(session.getChannel());
        });

    }
}
